/**
 * Copyright (C) 2025 pony working team
 * SPDX-License-Identifier: MulanPSL-2.0
 */

/* includes (local) ----------------------------------------------------------*/
#include "x_ring.h"
/* includes (standard library, system) ---------------------------------------*/
#include <stdlib.h>
#include <string.h>
/* includes (other library) --------------------------------------------------*/
/* includes (project) --------------------------------------------------------*/
#include <x_common.h>

/* defines -------------------------------------------------------------------*/
/* typedefs ------------------------------------------------------------------*/
/* macro ---------------------------------------------------------------------*/
/* variables (global) --------------------------------------------------------*/
/* variables (extern) --------------------------------------------------------*/
/* variables (local) ---------------------------------------------------------*/
/* inlines -------------------------------------------------------------------*/
/* functions -----------------------------------------------------------------*/

/**
 * ring_init - initialize a predefined ring using preallocated buffer
 * @ring: the predefined ring to be used.
 * @buf: the preallocated buffer to be used.
 * @size: the size of the preallocated buffer, must be power of 2.
 *
 * return: 0 on success, negative value on error
 */
int ring_init(ring_t *ring, unsigned char *buf, size_t size)
{
    if ((ring == NULL) || (buf == NULL)) {
        return -1;
    }

    if (size & (size - 1)) {
        return -1;
    }

    ring->buf = buf;
    ring->size = size;
    ring->head = 0;
    ring->tail = 0;

    return 0;
}

/**
 * ring_create - create ring with a specificed size
 * @size: the size of the buffer to be allocated, must be power of 2.
 *
 * return: a pointer to the ring allocated, NULL on error
 */
ring_t *ring_create(size_t size)
{
    ring_t *ring;

    if (size & (size - 1)) {
        return NULL;
    }

    ring = (ring_t *)malloc(sizeof(ring_t) + size);
    if (ring == NULL) {
        return NULL;
    }

    ring->buf = (unsigned char *)(&ring[1]);
    ring->size = size;
    ring->head = 0;
    ring->tail = 0;

    return ring;
}

/**
 * ring_destroy - destroy a ring
 * @size: the ring to be destroyed, must be created by ring_create
 *
 * return: NA
 */
void ring_destroy(ring_t *ring)
{
    if (ring == NULL) {
        return;
    }

    free(ring);
}

/**
 * __ring_put - put data to a ring, and the free space is enough
 */
static inline size_t __ring_put(ring_t *ring, unsigned char *buf, size_t len)
{
    size_t end_space;
    size_t mask = ring->size - 1;

    /*
     * end_space => the space size from head to the end of the ring
     *              (size - (head & mask))
     */
    end_space = ring->size - (ring->head & mask);

    if (len > end_space) {
        /*
         * must be this condition:
         *
         *        tail     head
         * +------+--------+---------+
         * |      |////////|         |
         * +------+--------+---------+
         *                 \         /
         *                   `--- end_space
         *
         * len <= free_space (already guaranteed in parent function)
         * len >  end_space
         *
         * so there must be two free partitions, one in the front, one in the end
         */
        memcpy(ring->buf + (ring->head & mask), buf, end_space);
        memcpy(ring->buf, buf + end_space, len - end_space);
    } else {
        /*
         * may be the following two conditions:
         *
         * 1)
         *        tail     head
         * +------+--------+---------+
         * |      |////////|         |
         * +------+--------+---------+
         *
         * no problem, it's fine:
         *
         *       free_space >= end_space >= len
         *
         * 2)
         *        head     tail
         * +------+--------+---------+
         * |//////|        |/////////|
         * +------+--------+---------+
         *
         * there is only one free region, and:
         *
         *       a) len <= free_space
         *       b) end_space > free_space
         *
         * so it is still safe to copy <len> bytes
         */
        memcpy(ring->buf + (ring->head & mask), buf, len);
    }

    ring->head += len;

    return len;
}

/**
 * ring_put - put data to a ring
 * @ring: the ring which the data will be put in.
 * @buf: the data buffer.
 * @len: the length of the data.
 *
 * return: the size of data actually put
 */
size_t ring_put(ring_t *ring, unsigned char *buf, size_t len)
{
    return __ring_put(ring, buf, min(len, ring->size - ring_len(ring)));
}

/**
 * ring_put_force - put data to a ring forcely
 * @ring: the ring which the data will be put in.
 * @buf: the data buffer.
 * @len: the length of the data.
 *
 * return: the size of data actually put
 */
size_t ring_put_force(ring_t *ring, unsigned char *buf, size_t len)
{
    size_t free_space;

    len = min(len, ring->size);
    free_space = ring->size - ring_len(ring);

    if (len > free_space) {
        ring->tail += len - free_space;
    }

    return __ring_put(ring, buf, len);
}

/**
 * ring_putc - put one byte to a ring
 * @ring: the ring which the data will be put in.
 * @byte: the byte to be put in.
 *
 * return: the size of data actually put
 */
size_t ring_putc(ring_t *ring, unsigned char byte)
{
    if (ring_full(ring)) {
        return 0;
    }

    ring->buf[ring->head & (ring->size - 1)] = byte;

    ring->head++;

    return 1;
}

/**
 * ring_putc_force - put data to a ring forcely
 * @ring: the ring which the data will be put in.
 * @byte: the byte to be put in.
 *
 * return: 1
 */
size_t ring_putc_force(ring_t *ring, unsigned char byte)
{
    if (ring_full(ring)) {
        ring->tail++;
    }

    ring->buf[ring->head & (ring->size - 1)] = byte;

    ring->head++;

    return 1;
}

/**
 * ring_get - get data from a ring
 * @ring: the ring which the data will be get from.
 * @buf: the data buffer.
 * @len: the max length of the data to get.
 *
 * return: the size of data actually got
 */
size_t ring_get(ring_t *ring, unsigned char *buf, size_t len)
{
    size_t data_space;
    size_t end_space;
    size_t mask = ring->size - 1;

    /*
     * data_space => the data size in the ring
     * end_space  => the space size from tail to the end of the ring
     *               (size - (tail & mask))
     */
    data_space = ring_len(ring);
    end_space = ring->size - (ring->tail & mask);

    len = min(len, data_space);

    if (len > end_space) {
        /*
         * must be this condition:
         *
         *        head     tail
         * +------+--------+---------+
         * |//////|        |/////////|
         * +------+--------+---------+
         *                 \         /
         *                  `--- end_space
         *
         * len <= data_space
         * len >  end_space
         *
         * so there must be two data partitions, one in the front, one in the end
         */
        memcpy(buf, ring->buf + (ring->tail & mask), end_space);
        memcpy(buf + end_space, ring->buf, len - end_space);
    } else {
        /*
         * may be the following two conditions:
         *
         * 1)
         *        head     tail
         * +------+--------+---------+
         * |//////|        |/////////|
         * +------+--------+---------+
         *
         * no problem, it's fine:
         *
         *       data_space >= end_space >= len
         *
         * 2)
         *        tail     head
         * +------+--------+---------+
         * |      |////////|         |
         * +------+--------+---------+
         *
         * there is only one data region, and:
         *
         *       a) len <= data_space
         *       b) end_space > data_space
         *
         * so it is still safe to copy <len> bytes
         */
        memcpy(buf, ring->buf + (ring->tail & mask), len);
    }

    ring->tail += len;

    return len;
}

/**
 * ring_getc - get one byte from a ring
 * @ring: the ring which the data will be put in.
 * @byte: the address where store the byte.
 *
 * return: the size of data actually got
 */
size_t ring_getc(ring_t *ring, unsigned char *byte)
{
    if (ring_empty(ring)) {
        return 0;
    }

    *byte = ring->buf[ring->tail & (ring->size - 1)];

    ring->tail++;

    return 1;
}
